package org.vectrics.employee;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.vectrics.company.CompanyPerson;
import org.vectrics.company.CompanyService;
import org.vectrics.location.Location;
import org.vectrics.recruiter.RecruitServiceLocator;
import org.vectrics.recruiter.job.Job;
import org.vectrics.recruiter.job.JobFunction;
import org.vectrics.recruiter.job.JobTerm;

import com.hexacta.hrs.domain.employee.history.AgreementCondition;
import com.hexacta.hrs.domain.employee.history.CareerRole;
import com.hexacta.hrs.domain.employee.history.EmploymentConditionHistory;
import com.hexacta.hrs.domain.feedback.FeedbackPeriod;
import com.hexacta.hrs.domain.feedback.Period;
import com.hexacta.hrs.domain.feedback.training.TrainingOcasion;
import com.hexacta.hrs.domain.staffing.Departamento;

/**
 * Clase que representa los datos de un empleado.
 * 
 * @author maguero
 * 
 */
public class Employee extends CompanyPerson {

    public static final String EVENT_CREATED = "hired.person";

    public static final String OBJECT_TYPE = "employee";

    private static final String NOT_EMPLOYEE = "not.employee";

    /** id */
    private Long id;

    /** Trabajo */
    private Job job;

    /** Obra social del empleado */
    private HealthInsurance healthInsurance;

    /** Persona para contactar en caso de emergencia */
    private String emergencyContact;

    /** Tel�fono para contactar en caso de emergencia */
    private String emergencyPhone;

    /**
     * Nota de contrataci�n, utilizado para agregar comentarios sobre las
     * condiciones de contrataci�n.
     */
    private String hiringNotes;

    /** Lista de estados del empleado */
    private Set<EmployeeStatus> status = new HashSet<EmployeeStatus>();

    /**
     * Location propia del empleado, en que lugar se encuentra la oficina en que
     * trabaja
     */
    private Location location = null;

    /**
     * Funcion por la cual fue contratada la persona, no siempre es igual al del
     * job order.
     */
    private JobFunction function = null;

    /** Horas que el empleado trabaja por d�a. */
    private Double hoursPerDay = null;

    /** Porcentaje dentro de la banda en la que esta el empleado. */
    private Long careerRolePosition = null;

    /** Mentor asociado al empleado. */
    private Employee mentor = null;

    private String medicalInsurance;

    private Date lastAgreementDay;

    private EmploymentConditionHistory employmentConditionHistory;

    private Boolean elegibleMentor = Boolean.FALSE;

    private Boolean elegibleProjectMember = Boolean.TRUE;

    // Departamento al que corresponde el empleado.
    private Departamento departamento;

    private Set<FeedbackPeriod> feedbackPeriods = new HashSet<FeedbackPeriod>();

    private Set<TrainingOcasion> trainings = new HashSet<TrainingOcasion>();

    public Employee() {
        setEmploymentConditionHistory(new EmploymentConditionHistory());
    }

    // Devuelve true si se puede vender los servicios del empleado a otras
    // empresas.
    public boolean esVendible() {
        if (departamento == null)
            return true;

        return departamento.getVendible();
    }

    public FeedbackPeriod getCurrentFeedbackPeriod() {

        FeedbackPeriod result = null;

        for (FeedbackPeriod period : getFeedbackPeriods()) {
            if (!period.isClosed()) {
                result = period;
                break;
            }
        }

        return result;
    }

    public FeedbackPeriod getFeedbackPeriodForPeriod(final Period period) {
        for (FeedbackPeriod fp : getFeedbackPeriods()) {
            if (fp.getPeriod().getYear().equals(period.getYear()))
                return fp;
        }
        return null;
    }

    @Override
    public Long getId() {
        return id;
    }

    @Override
    public void setId(final Long id) {
        this.id = id;
    }

    /**
     * @return the medicalInsurance
     */
    public String getMedicalInsurance() {
        return medicalInsurance;
    }

    /**
     * @param medicalInsurance
     *            the medicalInsurance to set
     */
    public void setMedicalInsurance(final String medicalInsurance) {
        this.medicalInsurance = medicalInsurance;
    }

    /**
     * @return the lastAgreementDay
     */
    public Date getLastAgreementDay() {
        return lastAgreementDay;
    }

    /**
     * @param lastAgreementDay
     *            the lastAgreementDay to set
     */
    public void setLastAgreementDay(final Date lastAgreementDay) {
        this.lastAgreementDay = lastAgreementDay;
    }

    public Job getJob() {
        return job;
    }

    public void setJob(final Job job) {
        this.job = job;
        if (job != null && getFunction() == null) {
            setFunction(job.getFunction());
        }
    }

    public HealthInsurance getHealthInsurance() {
        return healthInsurance;
    }

    public void setHealthInsurance(final HealthInsurance healthInsurance) {
        this.healthInsurance = healthInsurance;
    }

    public String getEmergencyContact() {
        return emergencyContact;
    }

    public void setEmergencyContact(final String emergencyContact) {
        this.emergencyContact = emergencyContact;
    }

    public String getEmergencyPhone() {
        return emergencyPhone;
    }

    public void setEmergencyPhone(final String emergencyPhone) {
        this.emergencyPhone = emergencyPhone;
    }

    public Set<EmployeeStatus> getStatus() {
        return status;
    }

    public void setStatus(final Set<EmployeeStatus> status) {
        this.status = status;
    }

    /**
     * @return the hiringNotes
     */
    public String getHiringNotes() {
        return hiringNotes;
    }

    /**
     * @param hiringNotes
     *            the hiringNotes to set
     */
    public void setHiringNotes(final String hiringNotes) {
        this.hiringNotes = hiringNotes;
    }

    /**
     * @return the function
     */
    public JobFunction getFunction() {
        return function;
    }

    /**
     * @param function
     *            the function to set
     */
    public void setFunction(final JobFunction function) {
        this.function = function;
    }

    /**
     * @return the location
     */
    public Location getLocation() {
        return location;
    }

    /**
     * @param location
     *            the location to set
     */
    public void setLocation(final Location location) {
        this.location = location;
    }

    public Double getHoursPerDay() {
        return hoursPerDay;
    }

    public void setHoursPerDay(final Double hoursPerDay) {
        this.hoursPerDay = hoursPerDay;
    }

    public Long getCareerRolePosition() {
        return careerRolePosition;
    }

    public void setCareerRolePosition(final Long careerRolePosition) {
        this.careerRolePosition = careerRolePosition;
    }

    public void setDepartamento(final Departamento departamento) {
        this.departamento = departamento;
    }

    public Departamento getDepartamento() {
        return departamento;
    }

    /**
     * Retorna el mentor actualmente asignado al empleado.
     * 
     * @return Employee con el mentor.
     */
    public Employee getMentor() {
        return mentor;
    }

    public void setMentor(final Employee mentor) {
        this.mentor = mentor;
    }

    /**
     * Retorna la fecha en que el empleado pasa a RD y null si no est� en ese
     * estado. Si el empleado fue contratado m�s de una vez en la historia de la
     * empresa, devuelve la �ltima fecha en que figura con el estado RD.
     * 
     * @return
     */
    public Date getFirstEmployeeDate() {
        Date resultado = null;
        for (EmployeeStatus employeeStatus : getStatus()) {
            if (employeeStatus.getStatusType().getIsEmployee()) {
                if (resultado == null) {
                    resultado = employeeStatus.getDateStart();
                } else {
                    if (resultado.before(employeeStatus.getDateStart())) {
                        resultado = employeeStatus.getDateStart();
                    }
                }
            }
        }
        return resultado;
    }

    /**
     * Retorna la fecha en la que el empleado fue contratado. Si el empleado fue
     * contratado m�s de una vez en la historia de la empresa, devuelve la
     * �ltima fecha de contratacion.
     * 
     * @return
     */
    public Date getFirstDate() {
        Date resultado = null;
        for (EmployeeStatus employeeStatus : getStatus()) {
            if (resultado == null) {
                resultado = employeeStatus.getDateStart();
            } else {
                if (resultado.before(employeeStatus.getDateStart())) {
                    resultado = employeeStatus.getDateStart();
                }
            }
        }
        return resultado;
    }

    /**
     * Retorna el estado actual del empleado, si no tiene estado retorna null.
     */
    public EmployeeStatus getCurrentStatus() {
        EmployeeStatus resultado = null;
        if (status == null) {
            System.out.println("null");
        }
        int cantidad = status.size();
        for (EmployeeStatus employeeStatus : getStatus()) {
            if (employeeStatus.getDateEnd() == null) {
                resultado = employeeStatus;
                break;
            }
        }
        return resultado;
    }

    /** Se agrega el estado al empleado. */
    public void addStatus(final JobTerm jobTerm, final Date fechaForm) {
        EmployeeStatus employeeStatus = new EmployeeStatus();
        employeeStatus.setDateStart(fechaForm);
        employeeStatus.setStatusType(jobTerm);
        if (getCurrentStatus() != null) {
            getCurrentStatus().setDateEnd(fechaForm);
        }
        getStatus().add(employeeStatus);
    }

    public boolean isValidDateforCurrentStatus(final Date date) {
        Date max = getMaxEndDate();
        if (max != null)
            return max.after(date);
        return false;
    }

    private Date getMaxEndDate() {
        Date resultado = null;
        for (EmployeeStatus employeeStatus : getStatus()) {
            if (employeeStatus.getDateEnd() != null) {
                if (resultado == null) {
                    resultado = employeeStatus.getDateEnd();
                }
                if (resultado.before(employeeStatus.getDateEnd())) {
                    resultado = employeeStatus.getDateEnd();
                }
            }
        }
        return resultado;
    }

    public EmployeeStatus getPreviousStatus() {
        EmployeeStatus resultado = null;
        Date date = null;
        for (EmployeeStatus employeeStatus : getStatus()) {
            if (employeeStatus.getDateEnd() != null) {
                if (resultado == null) {
                    date = employeeStatus.getDateEnd();
                    resultado = employeeStatus;
                }
                if (date.before(employeeStatus.getDateEnd())) {
                    date = employeeStatus.getDateEnd();
                    resultado = employeeStatus;
                }
            }
        }
        return resultado;
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder().append(getPerson()).append(getCompany()).toHashCode();
    }

    public EmploymentConditionHistory getEmploymentConditionHistory() {
        return employmentConditionHistory;
    }

    public EmploymentConditionHistory createEmploymentConditionHistoryIfIsNull() {
        if (employmentConditionHistory == null) {
            employmentConditionHistory = new EmploymentConditionHistory();
        }
        return employmentConditionHistory;
    }

    public void setEmploymentConditionHistory(final EmploymentConditionHistory employmentConditionHistory) {
        this.employmentConditionHistory = employmentConditionHistory;
    }

    public void addMentorAssignment(final Employee mentor, final Date effectiveDate) {
        if (!mentor.equals(this.mentor)) {
            createEmploymentConditionHistoryIfIsNull().addMentorAssignment(mentor, effectiveDate);
            setMentor(getEmploymentConditionHistory().getCurrentMentor());
        }
    }

    public Boolean isElegibleMentor() {
        return elegibleMentor;
    }

    public void setElegibleMentor(final Boolean elegibleMentor) {
        this.elegibleMentor = elegibleMentor;
    }

    @Override
    public boolean equals(final Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (!getClass().isAssignableFrom(obj.getClass()))
            return false;
        Employee other = (Employee) obj;

        // return new EqualsBuilder().append(this.getPerson(),
        // other.getPerson()).append(this.getCompany(),
        // other.getCompany()).isEquals();
        return new EqualsBuilder().append(getPerson(), other.getPerson()).isEquals();
    }

    // Actualiza el empleado segun lo ultimo del historial
    public void update(final Date date) {
        // Actualizar mentor
        Employee mentor = getEmploymentConditionHistory().getCurrentMentor();
        setMentor(mentor);

        CareerRole careerRole = getEmploymentConditionHistory().getCurrentCareerRoles();

        if (careerRole != null) {
            setFunction(careerRole.getCareerRole());
            setCareerRolePosition(careerRole.getCareerRolePosition());

            JobTerm jobTerm = careerRole.getCondition();

            CompanyService company = RecruitServiceLocator.getCompanyService();
            EmployeeStatus empStatus = getCurrentStatus();
            JobTerm statusType = null;
            JobTermReference reference = null;
            if (empStatus != null) {
                statusType = empStatus.getStatusType();
                reference = company.findJobTermReference(jobTerm, statusType);
            }
            if (reference != null && reference.isRequieredDate()) {
                addStatus(jobTerm, date);
            } else {
                addStatus(jobTerm, getDateStart());
            }

            if (jobTerm.getCode().trim().equals(NOT_EMPLOYEE)) {
                // TODO: Eliminar data end.
                setDateEnd(date);
            } else {
                setDateEnd(null);
            }
        }

        AgreementCondition currentAgreementCondition = getEmploymentConditionHistory().getCurrentAgreementConditions();

        if (currentAgreementCondition != null) {
            setHoursPerDay(currentAgreementCondition.getHoursPerDay());
            setLocation(currentAgreementCondition.getLocation());
            setMedicalInsurance(currentAgreementCondition.getMedicalInsurance());
        }

        Boolean assignable = getEmploymentConditionHistory().getCurrentAssignable();
        if (assignable != null) {
            setElegibleProjectMember(assignable);
        }

    }

    @Override
    public String toString() {
        return "Name: " + getPerson().getFirstName();
    }

    public void setElegibleProjectMember(final Boolean elegibleProjectMember) {
        this.elegibleProjectMember = elegibleProjectMember;
    }

    public Boolean isElegibleProjectMember() {
        return elegibleProjectMember;
    }

    /**
     * @return the feedbackPeriods
     */
    public Set<FeedbackPeriod> getFeedbackPeriods() {
        return feedbackPeriods;
    }

    /**
     * @param feedbackPeriods
     *            the feedbackPeriods to set
     */
    public void setFeedbackPeriods(final Set<FeedbackPeriod> feedbackPeriods) {
        this.feedbackPeriods = feedbackPeriods;
    }

    /**
     * @param period
     */
    public void addFeedbackPeriod(final FeedbackPeriod period) {
        period.setEmployee(this);
        feedbackPeriods.add(period);
        period.setEmployee(this);
    }

    /**
     * @return the trainings
     */
    public Set<TrainingOcasion> getTrainings() {
        return trainings;
    }

    /**
     * @param trainings
     *            the trainings to set
     */
    public void setTrainings(final Set<TrainingOcasion> trainings) {
        this.trainings = trainings;
    }

    /**
     * @param trainingOcasion
     */
    public void addTrainingOcasion(final TrainingOcasion trainingOcasion) {
        trainings.add(trainingOcasion);
    }

    /**
     * Calcula la anitg�edad del empleado desde el first working day, hasta el
     * corriente d�a.
     * 
     * Devuelve una cadena con la cantidad de a�os seguida de "y", y la cantidad
     * de meses seguidos de "m".
     * 
     * @return
     */
    public String getEmployeeAntiquity() {
        Date first = getDateStart();

        Calendar actualDate = Calendar.getInstance();
        Calendar firstDay = Calendar.getInstance();
        firstDay.setTime(first);

        actualDate.add(GregorianCalendar.YEAR, -firstDay.get(GregorianCalendar.YEAR));
        actualDate.add(GregorianCalendar.MONTH, -firstDay.get(GregorianCalendar.MONTH));
        actualDate.add(GregorianCalendar.DAY_OF_MONTH, -firstDay.get(GregorianCalendar.DAY_OF_MONTH));

        int months = actualDate.get(GregorianCalendar.MONTH);
        int years = actualDate.get(GregorianCalendar.YEAR);

        if (Calendar.getInstance().get(GregorianCalendar.YEAR) == firstDay.get(GregorianCalendar.YEAR)) {
            years -= 1;
        }

        if (years != 0 && months > 0)
            return years + " y, " + months + " m";
        if (years == 0)
            return months + " m";
        if (months == 0)
            return years + " y ";
        return null;
    }

    /**
     * Devuelve la cantidad de meses desde que el empleado trabaja en RD hasta
     * el 31/12 del corriente a�o.
     * 
     * @return
     */
    public int getEmployeeeDependencyEndYearAntiquity(final Integer year) {
        Date first = getDateStart();

        Calendar endYear = GregorianCalendar.getInstance();
        endYear.set(GregorianCalendar.DAY_OF_YEAR, 365);

        Calendar firstDay = GregorianCalendar.getInstance();
        firstDay.setTime(first);
        endYear.set(GregorianCalendar.YEAR, year);
        endYear.add(GregorianCalendar.YEAR, -firstDay.get(GregorianCalendar.YEAR));
        endYear.add(GregorianCalendar.MONTH, -firstDay.get(GregorianCalendar.MONTH));
        endYear.add(GregorianCalendar.DAY_OF_MONTH, -firstDay.get(GregorianCalendar.DAY_OF_MONTH));

        int months = endYear.get(GregorianCalendar.MONTH);
        int years = endYear.get(GregorianCalendar.YEAR);

        // Se agrego este if, dado que cuando se sustrae el mismo a�o, el valor
        // minimos es 1.
        if (year == firstDay.get(GregorianCalendar.YEAR)) {
            years -= 1;
        }
        months += years * 12;

        return months;
    }

    public boolean isEmployeeOltherThanFiveMonth() {
        Date first = getDateStart();
        Calendar actualDate = Calendar.getInstance();
        actualDate.add(Calendar.MONTH, -5);
        return first.before(actualDate.getTime());
    }

    /**
     * Calcula la anitg�edad del empleado desde el first working day, hasta el
     * 31/12 del corriente a�o.
     * 
     * Devuelve una cadena con la cantidad de a�os seguida de "y", y la cantidad
     * de meses seguidos de "m". 7
     * 
     * @return
     */
    public String getEmployeeEndYearAntiquity(final Integer year) {
        Date first = getDateStart();

        Calendar endYear = GregorianCalendar.getInstance();
        endYear.set(GregorianCalendar.DAY_OF_YEAR, 365);

        Calendar firstDay = GregorianCalendar.getInstance();
        firstDay.setTime(first);
        endYear.set(GregorianCalendar.YEAR, year);
        endYear.add(GregorianCalendar.YEAR, -firstDay.get(GregorianCalendar.YEAR));
        endYear.add(GregorianCalendar.MONTH, -firstDay.get(GregorianCalendar.MONTH));
        endYear.add(GregorianCalendar.DAY_OF_MONTH, -firstDay.get(GregorianCalendar.DAY_OF_MONTH));

        int months = endYear.get(GregorianCalendar.MONTH);
        int years = endYear.get(GregorianCalendar.YEAR);

        // Se agrego este if, dado que cuando se sustrae el mismo a�o, el valor
        // minimos es 1.
        if (year == firstDay.get(GregorianCalendar.YEAR)) {
            years -= 1;
        }

        if (years != 0 && months > 0)
            return years + " y, " + months + " m";
        if (years == 0)
            return months + " m";
        if (months == 0)
            return years + " y ";
        return null;
    }

    /**
     * @param nextEqual
     * @return
     */
    public boolean isAssignable(final Date nextEqual) {
        return getEmploymentConditionHistory().getAssignable(nextEqual);
    }
}
